package datatypes;

import static controller.Globals.*;
import static tools.Tool.leftOfTest;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.util.Set;
import java.util.TreeSet;

import Gui.TriangleDisplay;

/**
 * Representation of a point in the plane. 
 * @author PSeiferth
 *
 */
public class Vertex implements Comparable<Vertex>, Cloneable {
	/**
	 * The index is managed by the {@link IndexedFaceSet} class.
	 * Used for fast lookup.
	 */
	public int index = -1;
	public boolean selected = false;
	public boolean displayCoordinates = false;
	public int userFlags = 0;
	
	/**
	 * This Set of adjacentEdges is maintained by
	 * {@link IndexedFaceSet}. Please do not modify
	 * its contents!
	 */
	private Set<Edge> adjacentEdges;
	
	private Double x,y = null;
	
	/**
	 * Generates a Point in the R^2 with x-coordinate x and y-coordinate y.
	 * @param x
	 * @param y
	 */
	public Vertex(Double x, Double y) {
		this.setX(x);
		this.setY(y);
	}

	public void setX(Double x) {
		this.x = x;
	}

	public Double getX() {
		return x;
	}

	public void setY(Double y) {
		this.y = y;
	}

	public Double getY() {
		return y;
	}
	
	public void setAdjacentEdges(Set<Edge> adjacentEdges) {
		this.adjacentEdges = adjacentEdges;
	}

	public Set<Edge> getAdjacentEdges() {
		if (adjacentEdges == null) {
			adjacentEdges = new TreeSet<Edge>();
		}
		return adjacentEdges;
	}
		
	public boolean isInCircle(Vertex center, int radius)
	{
		return (center.x-radius < x && center.x + radius > x && center.y-radius < y && center.y+radius > y); 
	}
	
//	public Vertex toCanvasCoordinates(int width, int height, int viewportWidth, int viewportHeight)
//	{
//		float widthRatio = ((float)width) / viewportWidth;
//		float heightRatio = ((float)height) / viewportHeight;
//		return new Vertex((Double)(x*widthRatio), (Double)(height - (y*heightRatio)));
//	}
//	
//	public void updateLocationFromCanvasCoordinates(Vertex canvasPos, int width, int height, int viewportWidth, int viewportHeight)
//	{
//		Double widthRatio = ((double)width) / viewportWidth;
//		Double heightRatio = ((double)height) / viewportHeight;
//		this.x = (Double)(canvasPos.x/widthRatio);
//		this.y = (Double)((height-canvasPos.y)/heightRatio);
//	}
	
	public double distance(Vertex other) {
		return Math.sqrt(distanceSquared(other));
	}
	
	/**
	 * 
	 * @param triangle
	 * @param epsilon The precision. Higher values shrink the actual triangle tested.
	 * @return
	 */
	public boolean isInside(Triangle triangle, double epsilon) {
		Edge base = triangle.edge1;
		Vertex peak = triangle.getVertexNotOnEdge(base);
		if (leftOfTest(base, peak) > 0) {
			if (leftOfTest(base, this) < epsilon) {
				return false;
			}
			if (leftOfTest(base.getVertex2(), peak, this) < epsilon) {
				return false;
			}
			if (leftOfTest(peak, base.getVertex1(), this) < epsilon) {
				return false;
			}
		} else {
			if (leftOfTest(base, this) > -epsilon) {
				return false;
			}
			if (leftOfTest(base.getVertex2(), peak, this) > -epsilon) {
				return false;
			}
			if (leftOfTest(peak, base.getVertex1(), this) > -epsilon) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 
	 * @param v1
	 * @param v2
	 * @param v3
	 * @param epsilon The precision. Higher values shrink the actual triangle tested.
	 * @return
	 */
	public boolean isInside(Vertex v1, Vertex v2, Vertex v3, double epsilon) {
		if (leftOfTest(v1, v2, v3) > 0) {
			if (leftOfTest(v1, v2, this) < epsilon) {
				return false;
			}
			if (leftOfTest(v2, v3, this) < epsilon) {
				return false;
			}
			if (leftOfTest(v3, v1, this) < epsilon) {
				return false;
			}
		} else {
			if (leftOfTest(v1, v2, this) > -epsilon) {
				return false;
			}
			if (leftOfTest(v2, v3, this) > -epsilon) {
				return false;
			}
			if (leftOfTest(v3, v1, this) > -epsilon) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 
	 * @param base
	 * @param v3
	 * @param epsilon The precision. Higher values shrink the actual triangle tested.
	 * @return
	 */
	public boolean isInside(Edge base, Vertex v3, double epsilon) {
		return isInside(base.getVertex1(), base.getVertex2(), v3, epsilon);
	}

	@Override
	public int compareTo(Vertex o) {
		if (this.getX() < o.getX()) return -1;
		if (this.getX() > o.getX()) return 1;
		//if x-coordinates are equal compare y-coordinates.
		if (this.getY()< o.getY()) return -1;
		if (this.getY() > o.getY()) return 1;
		//points are equal, return 0
		return 0;
	}	
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if ( !(obj instanceof Vertex)) return false;
		Vertex that = (Vertex) obj;
		
		if (this.x.equals(that.x) && this.y.equals(that.y)) {
			return true;
		} else {
			return false;
		}
		
	}
	
	@Override
	public String toString() {
		return "("+this.x+", "+this.y+")";
	}

	
	@Override
	public Vertex clone() {
		Vertex clone = new Vertex(getX(), getY());
		return clone;
	}

	/**
	 * Returns the distance to the origin.
	 * @return
	 */
	public double norm() {
		return Math.sqrt(x*x + y*y);
	}
	
	/**
	 * Standard scalar product: this.x * other.x + this.y * other.y
	 * @param other
	 * @return
	 */
	public double multiplyWithVector(Vertex other) {
		return this.x * other.x + this.y * other.y;
	}
	
	public void minus(Vertex other) {
		this.x -= other.x;
		this.y -= other.y;
	}
	
	public void plus(Vertex other) {
		this.x += other.x;
		this.y += other.y;
	}
	
	public Vertex getMinus(Vertex other) {
		Vertex result = this.clone();
		result.minus(other);
		return result;
	}
	
	public Vertex getPlus(Vertex other) {
		Vertex result = this.clone();
		result.plus(other);
		return result;
	}	
	
	/**
	 * Returns a new vertex representing this vertex multiplied by the 
	 * given scalar factor. This Vertex will remain unchanged.
	 * @param factor
	 * @return
	 */
	public Vertex multiplyWithScalar(double factor) {
		Vertex result = this.clone();
		result.x *= factor;
		result.y *= factor;
		return result;
	}
	
	/**
	 * rounds the x and y values to the nearest long value.
	 */
	public void round() {
		x = (double) Math.round(x);
		y= (double) Math.round(y);
	}
	
	/**
	 * Vector addition: this.x += t.x; this.y += t.y
	 * @param t
	 */
	public void translate(Vertex t) {
		this.x += t.x;
		this.y += t.y;
	}
	
	// TODO: I don't know if I got the transform application right (the accessors are
	// pretty confusing), but scale should work. Check the others before using!!!!
	public void applyTransform(AffineTransform transform) {
		double xtemp = this.x*transform.getScaleX() + this.y*transform.getShearY();
		double ytemp = this.x*transform.getShearX() + this.y*transform.getScaleY();
		xtemp += transform.getTranslateX();
		ytemp += transform.getTranslateY();
		this.x = xtemp;
		this.y = ytemp;
	}
	
	
	public void draw(Graphics graphics, TriangleDisplay c) {
		// nb.: do not make permanent changes to the passed-in Graphics object!
		Graphics2D g = (Graphics2D) graphics.create();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		int size;
		if (selected) {
			size = VERTEX_SIZE_HIGHLIGHTED;
			g.setColor(Color.white);
		} else {
			size = VERTEX_SIZE_STANDARD;
			g.setColor(Color.black);
		}
		
		g.fillOval(this.getX().intValue()-size, this.getY().intValue()-size, 2*size, 2*size);
		if (selected) {
			g.setColor(Color.black);
			g.drawOval(this.getX().intValue()-size, this.getY().intValue()-size, 2*size, 2*size);
		}
		
		if (SHOW_VERTEX_ID) {
			g.drawString("" + index, this.getX().intValue(), this.getY().intValue());
		}
		if (displayCoordinates) {
			int x = this.getX().intValue();
			int y = this.getY().intValue();
			int h = 20;
			int w = 100;
			int r = 12;
			g.setColor(new Color(0, 0, 0, 100));
			g.fillRoundRect(x, y-h, w, h, r, r);
			g.setColor(Color.black);
			g.drawRoundRect(x, y-h, w, h, r, r);
			
			g.setColor(Color.white);
			g.drawString("" + x + "/" + y, x+5, y-5);
		}

	}

	public int getDimension() {
		// TODO Auto-generated method stub
		return 2;
	}

	public Double getEntry(int i) {
		switch (i) {
		case 0:
			return this.getX();
		case 1:
			return this.getY();
		default:
			return null;
		}
	}

	/**
	 * returns the squared distance between this vertex and the other vertex.
	 * @param other
	 * @return
	 */
	public double distanceSquared(Vertex other) {
		double deltaX = x - other.getX();
		double deltaY = y - other.getY();
		return deltaX * deltaX + deltaY * deltaY;
	}
	
}
